/*
 * FreeRTOS Kernel V11.1.0
 * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * https://www.FreeRTOS.org
 * https://github.com/FreeRTOS
 *
 */

/*
 * The FreeRTOS kernel's RISC-V port is split between the the code that is
 * common across all currently supported RISC-V chips (implementations of the
 * RISC-V ISA), and code which tailors the port to a specific RISC-V chip:
 *
 * + The code that is common to all RISC-V chips is implemented in
 *   FreeRTOS\Source\portable\GCC\RISC-V-RV32\portASM.S.  There is only one
 *   portASM.S file because the same file is used no matter which RISC-V chip is
 *   in use.
 *
 * + The code that tailors the kernel's RISC-V port to a specific RISC-V
 *   chip is implemented in freertos_risc_v_chip_specific_extensions.h.  There
 *   is one freertos_risc_v_chip_specific_extensions.h that can be used with any
 *   RISC-V chip that both includes a standard CLINT and does not add to the
 *   base set of RISC-V registers.  There are additional
 *   freertos_risc_v_chip_specific_extensions.h files for RISC-V implementations
 *   that do not include a standard CLINT or do add to the base set of RISC-V
 *   registers.
 *
 * CARE MUST BE TAKEN TO INCLDUE THE CORRECT
 * freertos_risc_v_chip_specific_extensions.h HEADER FILE FOR THE CHIP
 * IN USE.  To include the correct freertos_risc_v_chip_specific_extensions.h
 * header file ensure the path to the correct header file is in the assembler's
 * include path.
 *
 * This freertos_risc_v_chip_specific_extensions.h is for use on RISC-V chips
 * that include a standard CLINT and do not add to the base set of RISC-V
 * registers.
 *
 */

#include "portContext.h"

/* \brief 检查 freertos_risc_v_chip_specific_extensions.h 和/或命令行定义 */
#if defined(portasmHAS_CLINT) && defined(portasmHAS_MTIME)
    #error The portasmHAS_CLINT constant has been deprecated.  Please replace it with portasmHAS_MTIME.  portasmHAS_CLINT and portasmHAS_MTIME cannot both be defined at once.  See https://www.FreeRTOS.org/Using-FreeRTOS-on-RISC-V.html
#endif

#ifdef portasmHAS_CLINT
    #warning The portasmHAS_CLINT constant has been deprecated.  Please replace it with portasmHAS_MTIME and portasmHAS_SIFIVE_CLINT.  For now portasmHAS_MTIME and portasmHAS_SIFIVE_CLINT are derived from portasmHAS_CLINT.  See https://www.FreeRTOS.org/Using-FreeRTOS-on-RISC-V.html
    #define portasmHAS_MTIME portasmHAS_CLINT
    #define portasmHAS_SIFIVE_CLINT portasmHAS_CLINT
#endif

#ifndef portasmHAS_MTIME
    #error freertos_risc_v_chip_specific_extensions.h must define portasmHAS_MTIME to either 1 (MTIME clock present) or 0 (MTIME clock not present).  See https://www.FreeRTOS.org/Using-FreeRTOS-on-RISC-V.html
#endif

#ifndef portasmHAS_SIFIVE_CLINT
    #define portasmHAS_SIFIVE_CLINT 0
#endif

.global xPortStartFirstTask
.global pxPortInitialiseStack
.global freertos_risc_v_trap_handler
.global freertos_risc_v_exception_handler
.global freertos_risc_v_interrupt_handler
.global freertos_risc_v_mtimer_interrupt_handler

.extern vTaskSwitchContext
.extern xTaskIncrementTick
.extern pullMachineTimerCompareRegister
.extern pullNextTime
.extern uxTimerIncrementsForOneTick /* size_t 类型在 32 位内核上为 32 位，在 64 位内核上为 64 位 */
.extern xTaskReturnAddress
.extern Wed_tick_increase

.weak freertos_risc_v_application_exception_handler
.weak freertos_risc_v_application_interrupt_handler

/* \brief 更新机器定时器寄存器 */
.macro portUPDATE_MTIMER_COMPARE_REGISTER
    load_x a0, pullMachineTimerCompareRegister  /* 将比较寄存器地址加载到 a0 寄存器中 */
    load_x a1, pullNextTime                     /* 将 ullNextTime 地址加载到 a1 寄存器中 */

#if (__riscv_xlen == 32)
    /* 通过两次 32 位写操作更新 64 位机器定时器（mtimer）比较匹配值 */
    li a4, -1                                   /* 把 0xFFFFFFFF 加载到 a4 寄存器 */
    lw a2, 0(a1)                                /* 将 ullNextTime 的低 32 位字加载到 a2 寄存器中 */
    lw a3, 4(a1)                                /* 将 ullNextTime 的高 32 位字加载到 a3 寄存器中 */
    sw a4, 0(a0)                                /* 一开始低 32 位字不小于旧值，后续操作将覆盖该值 */
    sw a3, 4(a0)                                /* 将 ullNextTime 的高 32 位字写入比较寄存器，写入值不得小于新值 */
    sw a2, 0(a0)                                /* 将 ullNextTime 的低 32 位字写入比较寄存器 */
    lw t0, uxTimerIncrementsForOneTick          /* 将 ullTimerIncrementForOneTick 的值加载到 t0 寄存器中 */
    add a4, t0, a2                              /* 将 ullNextTime 的低 32 位字与 tick 的定时器增量值相加（假设单次节拍增量值不超过 32 位范围 */
    sltu t1, a4, a2                             /* 检查低 32 位字的和是否溢出 */
    add t2, a3, t1                              /* 将溢出位加到 ullNextTime 的高 32 位字中 */
    sw a4, 0(a1)                                /* 将低 32 位值写回 ullNextTime */
    sw t2, 4(a1)                                /* 将高 32 位值写回 ullNextTime */
#endif /* __riscv_xlen == 32 */

#if (__riscv_xlen == 64)
    /* 更新64位机器定时器（mtimer）的比较匹配值 */
    ld t2, 0(a1)                                /* 加载 ullNextTime 值到 t2 寄存器中 */
    sd t2, 0(a0)                                /* 更新比较寄存器的值位 ullNextTime */
    ld t0, uxTimerIncrementsForOneTick          /* 加载 uxTimerIncrementsForOneTick 值到 t0 寄存器中 */
    add t4, t0, t2                              /* ullNextTime 与 tick 的定时器增量值相加 */
    sd t4, 0(a1)                                /* 更新 ullNextTime 的值 */
#endif /* __riscv_xlen == 64 */
.endm

/**
 * \brief 初始化任务栈空间
 *
 *        与其他移植平台不同，pxPortInitialiseStack() 直接以汇编代码实现，
 *        因其需访问 portasmADDITIONAL_CONTEXT_SIZE 常量。该函数的原型与其他端口一致：

 *        StackType_t *pxPortInitialiseStack(StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters);
 *
 *
 *        pxPortInitialiseStack 参数说明：
 *        RISC-V ABI 调用规范：
 *            第一个参数，a0 (x10) 传入 pxTopOfStack（栈顶指针），返回时更新为新栈顶地址。
 *            第二个参数，a1 (x11) 传入 pxCode（任务函数地址）。
 *            第三个参数，a2 (x12) 传入 pvParameters（任务参数指针）。
 *
 *        寄存器映射 'I' 标准扩展（32位）使用 X1-X31 通用寄存器，
 *        'E' 精简扩展（嵌入式）仅使用 X1-X15，（当前假设为 'I' 扩展实现）
 *
 *         Register      ABI Name    Description                       Saver
 *         x0            zero        Hard-wired zero                   -
 *         x1            ra          Return address                    Caller
 *         x2            sp          Stack pointer                     Callee
 *         x3            gp          Global pointer                    -
 *         x4            tp          Thread pointer                    -
 *         x5-7          t0-2        Temporaries                       Caller
 *         x8            s0/fp       Saved register/Frame pointer      Callee
 *         x9            s1          Saved register                    Callee
 *         x10-11        a0-1        Function Arguments/return values  Caller
 *         x12-17        a2-7        Function arguments                Caller
 *         x18-27        s2-11       Saved registers                   Callee
 *         x28-31        t3-6        Temporaries                       Caller
 *
 *         RISC-V 上下文以如下堆栈帧形式保存到 FreeRTOS 任务中（当前假设全局指针 gp 和线程指针 tp 为常量，故不予保存）：
 *
 *         mstatus
 *         xCriticalNesting
 *         x31
 *         x30
 *         x29
 *         x28
 *         x27
 *         x26
 *         x25
 *         x24
 *         x23
 *         x22
 *         x21
 *         x20
 *         x19
 *         x18
 *         x17
 *         x16
 *         x15
 *         x14
 *         x13
 *         x12
 *         x11
 *         pvParameters
 *         x9
 *         x8
 *         x7
 *         x6
 *         x5
 *         portTASK_RETURN_ADDRESS
 *         [chip specific registers go here]
 *         [FPU registers go here]
 *         pxCode
 */
.balign 4
.type pxPortInitialiseStack, function
pxPortInitialiseStack:
    csrr t0, mstatus                     /* 获取当前 mstatus 寄存器的值 */
    andi t0, t0, ~0x8                    /* 确保在中断服务程序（ISR）内恢复堆栈时禁用中断。
                                          * 此操作在调度器启动后创建任务时为必需项，否则中断默认应保持禁用状态 */
#ifdef __riscv_flen
    addi t1, x0, 0x388                   /* 生成 0x3880 值，该值用于设置 mstatus 寄存器中的 MPIE 位和 MPP 位，并将 FS 位设为 1 */
#else
    addi t1, x0, 0x188                   /* 生成 0x1880 值，该值用于设置 mstatus 寄存器中的 MPIE 位和 MPP 位 */
#endif
    slli t1, t1, 4                       /* 将生成的值左移 4 位 */
    or t0, t0, t1                        /* 在 mstatus 的值中设置 MPIE 和 MPP 位 */

    addi a0, a0, -(portWORD_SIZE)
    store_x t0, 0(a0)                    /* 把 mstatus 值保存在栈顶 */
    addi a0, a0, -portWORD_SIZE          /* 移动空间存储临界区嵌套计数 */
    store_x x0, 0(a0)                    /* 每个任务的临界区嵌套计数初始值为 0 */

#ifdef __riscv_32e
    addi a0, a0, -(6 * portWORD_SIZE)    /* 移动空间存储 x10-x15 寄存器 */
#else
    addi a0, a0, -(22 * portWORD_SIZE)   /* 移动空间存储 x10-x31 寄存器 */
#endif
    store_x a2, 0(a0)                    /* 任务的参数（即 pvParameters 参数）会通过寄存器 X10/a0 传递并存储在堆栈中。 */
    addi a0, a0, -(6 * portWORD_SIZE)    /* 移动空间存储 x5-x9 寄存器和 taskReturnAddress（任务返回地址） */
    load_x t0, xTaskReturnAddress
    store_x t0, 0(a0)                    /* 存储任务返回地址 */
    addi t0, x0, portasmADDITIONAL_CONTEXT_SIZE /* 芯片特定的附加寄存器数量 */
chip_specific_stack_frame:               /* 首先将芯片特定的寄存器添加到正在构建的堆栈帧中 */
    beq t0, x0, 1f                       /* 没有芯片特定的寄存器需要存储 */
    addi a0, a0, -portWORD_SIZE          /* 移动空间存储芯片特定的寄存器 */
    store_x x0, 0(a0)                    /* 将芯片专用寄存器的初始值设置为零 */
    addi t0, t0, -1                      /* 减少剩余待处理的芯片专用寄存器计数 */
    j chip_specific_stack_frame          /* 直到没有芯片特定的寄存器 */
1:
#ifdef USE_SYSCALL_INTERRUPT_PRIORITY
    li   t0, 0x888
    store_x t0, 4(a0)                    /* MIE */
#endif
#ifdef __riscv_flen
    add a0, a0, -portFPU_CONTEXT_SIZE    /* 栈深加上 FPU 寄存器值总大小 */
    mv t2, a0
    addi t0, x0, portFPU_CONTEXT_SIZE    /* FPU 寄存器值总大小（字节） */
fpu_specific_stack_frame:
    beq t0, x0, 2f                       /* 没有额外的 FPU 寄存器需要保存 */
    store_x x0, 0(t2)                    /* 初始化为 0 */
    addi t2, t2, portWORD_SIZE           /* 移动空间存储 FPU 寄存器 */
    addi t0, t0, -portWORD_SIZE          /* 减少剩余待处理的 FPU 寄存器值字节数 */
    j fpu_specific_stack_frame           /* 直到没有 FPU 寄存器 */
2:
#endif
    addi a0, a0, -portWORD_SIZE
    store_x a1, 0(a0)                    /* 将 mret 返回值（即 pxCode 参数）压入堆栈 */
    ret

/**
 * \brief 启动第一个任务
 */
xPortStartFirstTask:
    load_x  sp, pxCurrentTCB                /* 加载当前运行的任务的 TCB */
    load_x  sp, 0(sp)                       /* 读取 TCB 第一个成员，即任务栈空间的栈顶地址 */

    load_x  x1, 0(sp)                       /* 关于启动调度器的特殊说明：
                                             * 此时会将异常返回地址作为函数的返回地址使用 */
#ifdef __riscv_flen
    portasmRESTORE_FPU_REGISTERS            /* 加载 FPU 寄存器 */
#endif
    portasmRESTORE_ADDITIONAL_REGISTERS     /* 该函数定义于 freertos_risc_v_chip_specific_extensions.h 头文件中，
                                             * 专门用于恢复 RISC-V 架构实现中特有的寄存器状态 */

    load_x  x7, 4 * portWORD_SIZE(sp)       /* 加载 x7/t2 寄存器 */
    load_x  x8, 5 * portWORD_SIZE(sp)       /* 加载 x8/s0/fp 寄存器 */
    load_x  x9, 6 * portWORD_SIZE(sp)       /* 加载 x9/s1 寄存器 */
    load_x  x10, 7 * portWORD_SIZE(sp)      /* 加载 x10/a0 寄存器 */
    load_x  x11, 8 * portWORD_SIZE(sp)      /* 加载 x11/a1 寄存器 */
    load_x  x12, 9 * portWORD_SIZE(sp)      /* 加载 x12/a2 寄存器 */
    load_x  x13, 10 * portWORD_SIZE(sp)     /* 加载 x13/a3 寄存器 */
    load_x  x14, 11 * portWORD_SIZE(sp)     /* 加载 x14/a4 寄存器 */
    load_x  x15, 12 * portWORD_SIZE(sp)     /* 加载 x15/a5 寄存器 */
#ifndef __riscv_32e
    load_x  x16, 13 * portWORD_SIZE(sp)     /* 加载 x16/a6 寄存器 */
    load_x  x17, 14 * portWORD_SIZE(sp)     /* 加载 x17/a7 寄存器 */
    load_x  x18, 15 * portWORD_SIZE(sp)     /* 加载 x18/s2 寄存器 */
    load_x  x19, 16 * portWORD_SIZE(sp)     /* 加载 x19/s3 寄存器 */
    load_x  x20, 17 * portWORD_SIZE(sp)     /* 加载 x20/s4 寄存器 */
    load_x  x21, 18 * portWORD_SIZE(sp)     /* 加载 x21/s5 寄存器 */
    load_x  x22, 19 * portWORD_SIZE(sp)     /* 加载 x22/s6 寄存器 */
    load_x  x23, 20 * portWORD_SIZE(sp)     /* 加载 x23/s7 寄存器 */
    load_x  x24, 21 * portWORD_SIZE(sp)     /* 加载 x24/s8 寄存器 */
    load_x  x25, 22 * portWORD_SIZE(sp)     /* 加载 x25/s9 寄存器 */
    load_x  x26, 23 * portWORD_SIZE(sp)     /* 加载 x26/s10 寄存器 */
    load_x  x27, 24 * portWORD_SIZE(sp)     /* 加载 x27/s11 寄存器 */
    load_x  x28, 25 * portWORD_SIZE(sp)     /* 加载 x28/t3 寄存器 */
    load_x  x29, 26 * portWORD_SIZE(sp)     /* 加载 x29/t4 寄存器 */
    load_x  x30, 27 * portWORD_SIZE(sp)     /* 加载 x30/t5 寄存器 */
    load_x  x31, 28 * portWORD_SIZE(sp)     /* 加载 x31/t6 寄存器 */
#endif

    load_x  x5, portCRITICAL_NESTING_OFFSET * portWORD_SIZE(sp)    /* 从任务的栈空间中获取该任务当前的 xCriticalNesting 值 */
    load_x  x6, pxCriticalNesting           /* 将 pxCriticalNesting 的地址加载到 x6 寄存器中 */
    store_x x5, 0(x6)                       /* 恢复当前任务的临界区嵌套值 */

    load_x  x5, portMSTATUS_OFFSET * portWORD_SIZE(sp)    /* 加载栈空间中的 mstatus 寄存器值到 x5 寄存器 */
    addi    x5, x5, 0x08                    /* 设置 MIE（Machine Interrupt Enable） 位，
                                             * 确保第一个任务启动时中断处于启用状态。这是必需的，因为任务启动
                                             * 是通过 ret 指令返回而非 eret（异常返回）指令 */
    csrrw   x0, mstatus, x5                 /* 中断使能 */

    load_x  x5, 2 * portWORD_SIZE(sp)       /* 初始化 x5/t0 值 */
    load_x  x6, 3 * portWORD_SIZE(sp)       /* 初始化 x6/t1 值 */

    addi    sp, sp, portCONTEXT_SIZE
    ret
/*-----------------------------------------------------------*/

freertos_risc_v_application_exception_handler:
    csrr t0, mcause     /* For viewing in the debugger only. */
    csrr t1, mepc       /* For viewing in the debugger only */
    csrr t2, mstatus    /* For viewing in the debugger only */
    j .
/*-----------------------------------------------------------*/

freertos_risc_v_application_interrupt_handler:
    csrr t0, mcause     /* For viewing in the debugger only. */
    csrr t1, mepc       /* For viewing in the debugger only */
    csrr t2, mstatus    /* For viewing in the debugger only */
    j .
/*-----------------------------------------------------------*/

.section .text.freertos_risc_v_exception_handler
freertos_risc_v_exception_handler:
    portcontextSAVE_EXCEPTION_CONTEXT
    /* a0 now contains mcause. */
    li t0, 11                           /* 11 == environment call. */
    bne a0, t0, other_exception         /* Not an M environment call, so some other exception. */
    call vTaskSwitchContext
    portcontextRESTORE_CONTEXT

other_exception:
    call freertos_risc_v_application_exception_handler
    portcontextRESTORE_CONTEXT
/*-----------------------------------------------------------*/

.section .text.freertos_risc_v_interrupt_handler
freertos_risc_v_interrupt_handler:
    portcontextSAVE_INTERRUPT_CONTEXT
    call freertos_risc_v_application_interrupt_handler
    portcontextRESTORE_CONTEXT
/*-----------------------------------------------------------*/

.section .text.freertos_risc_v_mtimer_interrupt_handler
freertos_risc_v_mtimer_interrupt_handler:
    portcontextSAVE_INTERRUPT_CONTEXT
    portUPDATE_MTIMER_COMPARE_REGISTER
    call xTaskIncrementTick
    beqz a0, exit_without_context_switch    /* Don't switch context if incrementing tick didn't unblock a task. */
    call vTaskSwitchContext
exit_without_context_switch:
    portcontextRESTORE_CONTEXT

/**
 * \brief FreeRTOS trap 处理函数
 */
.section .text.freertos_risc_v_trap_handler
.align 8
freertos_risc_v_trap_handler:
    portcontextSAVE_CONTEXT_INTERNAL      /* 保存环境 */

    csrr a0, mcause                       /* 读取 mcause 寄存器的值 */
    csrr a1, mepc                         /* 读取 mepc 寄存器的值（异常或中断时的程序计数器（PC）值） */

    bge a0, x0, synchronous_exception     /* 如果 mcause 寄存器的值大于等于 0 则跳转 */

    /* 中断 */
asynchronous_interrupt:
    store_x a1, 0(sp)                     /* 异步中断触发，需保存未经修改的任务执行函数返回地址 */
    csrr t0, mscratch                     /* 仅在非嵌套场景下保存栈指针（SP）*/
#if portIRQ_PREEMPTIVE                    /* 中断抢占式调度已启用，需在执行前检查当前处于中断上下文（ISR）还是线程上下文 */
    bne t0, x0, skip_switch_stack1
#endif
    load_x sp, xISRStackTop               /* 切换到 ISR 栈 */
skip_switch_stack0:
    addi t0, t0, 1
    csrw mscratch, t0                     /* 递增嵌套计数值，该寄存器用于标识当前处于中断（ISR）还是线程上下文 */
    j handle_interrupt

    /* 异常 */
synchronous_exception:
    addi a1, a1, 4                        /* 同步异常触发，需将异常返回地址更新至触发异常的指令的后一条指令地址 */
    store_x a1, 0(sp)                     /* 保存更新了的任务执行函数返回地址 */
    csrr t0, mscratch                     /* 仅在非嵌套场景下保存栈指针（SP）*/
#if portIRQ_PREEMPTIVE                    /* 中断抢占式调度已启用，需在执行前检查当前处于中断上下文（ISR）还是线程上下文 */
    bne t0, x0, skip_switch_stack1
#endif
    load_x sp, xISRStackTop               /* 切换到 ISR 栈 */
skip_switch_stack1:
    addi t0, t0, 1
    csrw mscratch, t0                     /* 递增嵌套计数值 */
    j handle_exception

    /* 中断处理 */
handle_interrupt:
#if (portasmHAS_MTIME != 0)
test_if_mtimer:                           /* 若存在 CLINT（核心本地中断控制器），则使用 mtimer（机器模式定时器）生成节拍中断 */
    addi t0, x0, 1
    slli t0, t0, __riscv_xlen - 1         /* 最低有效位（LSB）已置位，需移位至最高有效位（MSB）。32位内核移位31位，64位内核移位63位 */
    addi t1, t0, 7                        /* 0x8000[]0007 == 机器定时器中断 */
#if defined(USE_NONVECTOR_MODE) && (USE_NONVECTOR_MODE != 0)
    bne a0, t1, application_interrupt_handler
#else
    bne a0, t1, processed_source
#endif

    /* 更新机器定时器寄存器 */
    portUPDATE_MTIMER_COMPARE_REGISTER
    call xTaskIncrementTick
    call Wed_tick_increase
    beqz a0, processed_source             /* 若 tick 递增未解除任何任务的阻塞状态，则不执行上下文切换 */
    call vTaskSwitchContext               /* 切换任务 */
    j processed_source

#endif /* portasmHAS_MTIME */

#if defined(USE_NONVECTOR_MODE) && (USE_NONVECTOR_MODE != 0)
    /* 应用中断处理，只用于无中断向量表的模式 */
application_interrupt_handler:
    call freertos_risc_v_application_interrupt_handler
    j processed_source
#endif

    /* 异常处理 */
handle_exception:
    /* a0 contains mcause. */
    li t0, 11                                   /* 11 == environment call. */
    bne a0, t0, application_exception_handler   /* Not an M environment call, so some other exception. */
    csrr t0, mscratch
    /* Disable task scheduler, if it's already in ISR */
    addi t1, t0, -1
    bgtz t1, processed_source
    call vTaskSwitchContext
    j processed_source

application_exception_handler:
    call freertos_risc_v_application_exception_handler
    j processed_source                    /* No other exceptions handled yet. */

processed_source:
    portcontextRESTORE_CONTEXT            /* 恢复环境 */

